package src

import (
	"encoding/json"
	"strings"
	"sync"
	"time"

	"gitee.com/haifengat/goctp/v2"
	"golang.org/x/text/language"
	"golang.org/x/text/message"

	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"

	zd "gitee.com/haifengat/zorm-dm/v2"
)

var (
	instLastMin         sync.Map // 合约:map[string]interface{},最后1分钟数据
	mapInstrumentStatus sync.Map // 合约交易状态

	tradingDay       string                                              // 当前交易日
	actionDay        string                                              // 交易日起始交易日期
	actionDayNext    string                                              // 交易日起始交易日期-下一日
	products         []string                                            // 需要接收行情的品种（大写）
	instrumentStatus = map[string]goctp.TThostFtdcInstrumentStatusType{} // 合约状态
	trd              *goctp.TradePro
	md               *goctp.QuotePro
	isLogin          = false
	cntTicks         = 0 // 记录tick数量
)

func redisToPg() (err error) {
	// 取交易日
	tradingDay, err := rdb.HGet(ctxRedis, "tradingday", "curday").Result()
	if err != nil {
		return errors.Wrap(err, "get curday")
	}
	// 删除当前交易日数据(实现重复入库)
	_, err = zd.Delete[Bar](ctxDAO, map[string]any{"trading_day": tradingDay})
	if err != nil {
		return errors.Wrap(err, "delete")
	}
	// 取所有 key
	var bars = make([]Bar, 0, 5000)
	insts, err := rdb.Keys(ctxRedis, "*").Result()
	if err != nil {
		return errors.Wrap(err, "get redis keys")
	}
	// 按合约key入库
	for _, inst := range insts {
		if inst == "tradingday" {
			continue
		}
		var mins = []string{}
		if mins, err = rdb.LRange(ctxRedis, inst, 0, -1).Result(); err != nil {
			logrus.Error("取redis数据错误:", inst, err)
			continue
		}
		for _, bsMin := range mins {
			var bar Bar
			bsMin = strings.ReplaceAll(bsMin, "id", "DateTime")
			if err = json.Unmarshal([]byte(bsMin), &bar); err != nil {
				logrus.Error("解析bar错误:", bar, " ", err)
				continue
			}
			// bar.InsertTime = DateTimeType(time.Now().Local().Format(time.DateTime))
			// bar.UpdateTime = DateTimeType(time.Now().Local().Format(time.DateTime))
			bars = append(bars, *bar.Fix())
			if len(bars) >= 5000 { // 10W -> PostgreSQL only supports 65535 parameters(ZORM)
				n, err := zd.Insert[Bar](ctxDAO, false, bars...)
				if err != nil {
					return errors.Wrap(err, "create batch")
				}
				logrus.Infof("inserted bars: %d", n)
				bars = make([]Bar, 0, 5000)
			}
		}
	}
	if len(bars) > 0 {
		n, err := zd.Insert[Bar](ctxDAO, false, bars...)
		if err != nil {
			return errors.Wrap(err, "create batch")
		}
		logrus.Info("inserted bars: ", n)
	}
	return
}

func save2DB() {
	if trd != nil && isLogin {
		// 查询数据并入库
		mpAccount := trd.ReqQryTradingAccount()
		for _, acc := range mpAccount {
			field := new(AccountField).FromCTP(acc)
			field.InsertTime = DateTimeType(time.Now().Local().Format(time.DateTime))
			field.UpdateTime = DateTimeType(time.Now().Local().Format(time.DateTime))
			_, err := zd.UpdateAndInsert[AccountField](ctxDAO, zd.StructToMap(ctxDAO, *field, false, true))
			if err != nil {
				logrus.Infof("%+v", field)
				logrus.Error("update account error:", err.Error())
			}
		}
		orders := make([]map[string]any, 0)
		for _, ord := range trd.Orders {
			field := new(OrderField).FromCTP(ord)
			field.InsertTime = DateTimeType(time.Now().Local().Format(time.DateTime))
			field.UpdateTime = DateTimeType(time.Now().Local().Format(time.DateTime))
			orders = append(orders, zd.StructToMap(ctxDAO, *field, false, true))
		}
		if len(orders) > 0 {
			_, err := zd.UpdateAndInsert[OrderField](ctxDAO, orders...)
			if err != nil {
				logrus.Error("update order error:", err.Error())
			}
		}
		trades := make([]map[string]any, 0)
		for _, trds := range trd.Trades {
			for _, trd := range trds {
				field := new(TradeField).FromCTP(trd)
				field.InsertTime = DateTimeType(time.Now().Local().Format(time.DateTime))
				field.UpdateTime = DateTimeType(time.Now().Local().Format(time.DateTime))
				trades = append(trades, zd.StructToMap(ctxDAO, *field, false, true))
			}
		}
		if len(trades) > 0 {
			_, err := zd.UpdateAndInsert[TradeField](ctxDAO, trades...)
			if err != nil {
				logrus.Error("update trade error:", err.Error())
			}
		}
		posis := make([]map[string]any, 0)
		for _, p := range trd.ReqQryPosition() {
			field := new(PositionField).FromCTP(p)
			field.InsertTime = DateTimeType(time.Now().Local().Format(time.DateTime))
			field.UpdateTime = DateTimeType(time.Now().Local().Format(time.DateTime))
			posis = append(posis, zd.StructToMap(ctxDAO, *field, false, true))
		}
		if len(posis) > 0 {
			_, err := zd.UpdateAndInsert[PositionField](ctxDAO, posis...)
			if err != nil {
				logrus.Error("update position error:", err.Error())
			}
		}
	}
}

func Run724() {
	initEnv()
	startTrade()

	save2DB() // 保存初始数据

	tick := time.NewTicker(1 * time.Minute)
	for range tick.C {
		hhmm := time.Now().Local().Format("15:04")
		if isLogin { // 收盘后退出
			isContinue := false
			for _, v := range instrumentStatus {
				if v == goctp.THOST_FTDC_IS_Continous {
					isContinue = true
					save2DB()
					break
				}
			}
			if time.Now().Local().Minute()%15 == 0 { // 15 分钟显示一次
				p := message.NewPrinter(language.English)
				logrus.Infof("%s, 交易中:%v, ticks: %s", hhmm, isContinue, p.Sprintf("%d", cntTicks))
			}
			if !isContinue {
				hour := time.Now().Local().Hour()
				if hour <= 3 || hour == 15 { // 夜盘结束&当日收盘
					logrus.Info("release at ", time.Now().Local())
					releaseTrade()
					releaseQuote()
				}
				if hour == 15 {
					logrus.Info("保存数据到 pg")
					if err := redisToPg(); err != nil {
						logrus.Error(err)
					}
				}
			}
		} else { // 定时连接
			if time.Now().Local().Minute()%15 == 0 { // 15 分钟显示一次
				logrus.Info(hhmm, " 接口关闭")
			}
			if hhmm == "08:40" || hhmm == "20:40" {
				logrus.Info("relogin at ", hhmm)
				startTrade()
			}
		}
	}
}
